經過上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」的簡介,相信對Trigger Module的功能及相關Register應該有個簡單的了解了吧!?
今天呢! 為了要準備去跨年,所以我們講點輕鬆的~~!
來看看Debugger如何使用這個Trigger Module當作"Breakpoint"和"Watchpoint"來使用!
  
[2018/01/06] 修改初始化流程
  
上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」有提到幾個Trigger Module常用的Registers,這邊簡單的回顧一下
終於要來講實作的部分了!
由於每個Trigger都有可能支援各種不同的功能,因此建議初始化的流程如下:
參考以下程式碼(src/target/riscv/riscv.c)
int riscv_enumerate_triggers(struct target *target)
{
    RISCV_INFO(r);
    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid,
                GDB_REGNO_TSELECT);
        for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
            r->trigger_count[hartid] = t;
            ///譯註: Step 1. 對$tselect寫入目前選擇Trigger的編號
            riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
            uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TSELECT);
            /* Mask off the top bit, which is used as tdrmode in old
             * implementations. */
            tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
            
            ///譯註: Step 2. 重新讀回$tselect,確認該Trigger是否存在
            if (tselect_rb != t)
                break;
            ///譯註: Step 3. 讀取$tdata1,確認Trigger支援的功能
            uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TDATA1);
            int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
            switch (type) {
                case 1:
                    /* On these older cores we don't support software using
                     * triggers. */
                    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
                case 2:
                    if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
                        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
            }
        }
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
        LOG_INFO("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
    }
    return ERROR_OK;
}
  
  
主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Breakpoint的資料進來時
    if (breakpoint->type == BKPT_SOFT) {
        
        ....Software Breakpoint的部分省略,待日後說明~~!
    } else if (breakpoint->type == BKPT_HARD) {
        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);  ///譯註: 先準備好相關資料
        int result = add_trigger(target, &trigger);  ///譯註: 新增Trigger
        if (result != ERROR_OK)
            return result;
    } else {
        LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }
    breakpoint->set = true;
    return ERROR_OK;
再來看一下中間資料準備的過程
static void trigger_from_breakpoint(struct trigger *trigger,
        const struct breakpoint *breakpoint)
{
    trigger->address = breakpoint->address;
    trigger->length = breakpoint->length;
    trigger->mask = ~0LL;
    trigger->read = false;
    trigger->write = false;
    trigger->execute = true;    ///譯註: 這行很重要,用來標示這是個Breakpoint
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = breakpoint->unique_id;
}
最後是新增Trigger的部分,基本上就是把準備好的資料,一個個複製到對應的欄位中!
static int maybe_add_trigger_t2(struct target *target, unsigned hartid,
        struct trigger *trigger, uint64_t tdata1)
{
    RISCV_INFO(r);
    /* tselect is already set */
    if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
        /* Trigger is already in use, presumably by user code. */
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }
    /* address/data match trigger */
    tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
    tdata1 = set_field(tdata1, MCONTROL_ACTION,
            MCONTROL_ACTION_DEBUG_MODE);
    tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
    tdata1 |= MCONTROL_M;
    if (r->misa & (1 << ('H' - 'A')))
        tdata1 |= MCONTROL_H;
    if (r->misa & (1 << ('S' - 'A')))
        tdata1 |= MCONTROL_S;
    if (r->misa & (1 << ('U' - 'A')))
        tdata1 |= MCONTROL_U;
    if (trigger->execute)
        tdata1 |= MCONTROL_EXECUTE;
    if (trigger->read)
        tdata1 |= MCONTROL_LOAD;
    if (trigger->write)
        tdata1 |= MCONTROL_STORE;
    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);
    uint64_t tdata1_rb = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TDATA1);
    LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
    if (tdata1 != tdata1_rb) {
        LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
                PRIx64 " to tdata1 it contains 0x%" PRIx64,
                tdata1, tdata1_rb);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }
    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);
    return ERROR_OK;
}
  
  
一樣主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Watchpoint的資料進來時
int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
{
    struct trigger trigger;
    trigger_from_watchpoint(&trigger, watchpoint);  ///譯註: 先準備好相關資料
    int result = add_trigger(target, &trigger);     ///譯註: 新增Trigger
    if (result != ERROR_OK)
        return result;
    watchpoint->set = true;
    return ERROR_OK;
}
在過來看一下資料準備的部分
static void trigger_from_watchpoint(struct trigger *trigger,
        const struct watchpoint *watchpoint)
{
    trigger->address = watchpoint->address;
    trigger->length = watchpoint->length;
    trigger->mask = watchpoint->mask;
    trigger->value = watchpoint->value;
    trigger->read = (watchpoint->rw == WPT_READ || watchpoint->rw == WPT_ACCESS);   ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被讀取時比較
    trigger->write = (watchpoint->rw == WPT_WRITE || watchpoint->rw == WPT_ACCESS); ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被寫入時比較
    trigger->execute = false;
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = watchpoint->unique_id;
}
最後新增Trigger的部分同上面"# 2.2 新增Breakpoint",不多加贅述
由於Breakpoint和Watchpoint在移除Trigger的方式相同,這邊就統一一起講解比較快XD!
不囉唆,先上程式碼(src/target/riscv/riscv.c)
static int remove_trigger(struct target *target, struct trigger *trigger)
{
    RISCV_INFO(r);
    int first_hart = -1;
    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        if (first_hart < 0) {
            first_hart = hartid;
            break;
        }
    }
    assert(first_hart >= 0);
    unsigned int i;
    for (i = 0; i < r->trigger_count[first_hart]; i++) {
        ///譯註: 找到之前對應的那個Trigger的編號
        if (r->trigger_unique_id[i] == trigger->unique_id)
            break;
    }
    if (i >= r->trigger_count[first_hart]) {
        LOG_ERROR("Couldn't find the hardware resources used by hardware "
                "trigger.");
        return ERROR_FAIL;
    }
    LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
    for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
        ///譯註: 將Data全寫成0,這樣這個Trigger就被關閉了!
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
    }
    r->trigger_unique_id[i] = -1;
    return ERROR_OK;
}
幾本上就是在OpenOCD中,對於每個Breakpoint和Watchpoint都會給他一個unique_id,利用比較這個unique_id就可以快速找到這個Breakpoint/Watchpoint所對應的Trigger編號!
找到該編號用就利用以下步驟關閉他:
大概說明完整個Debug System內容的80%了,整體架構和功能也都有做個淺出的介紹,
詳細內容還是需要對照整份文件和程式碼來看會比較清楚。尤其是官方三不五時就在改Coding的方式 (翻桌
突然發覺是不是應該拆成3篇,這樣.....就可以安心去跨年了!!
  
  
  
Hi HelloWorld,
您好,我是初次接觸RISC-V的新手, 您這系列的文章實在是讓像我這樣的初學者獲益良多,感謝無私分享!
不過在您的文章中的srouce code,在我的理解上是著重在對於OpenOCD的介紹,與說明它如何下發指令來透過JTAG與RISC-V IC裡的debug module溝通,而IC中debug module, debug transprot module, debug module interface...等,則是另外IC設計的範疇,當然還是須配合RISC-V External Debug Support文件來實做。
不知道我這樣的理解是否正確,不過這系列文章已有些時日了,如過您有看到還是希望您能幫我解答一下。感謝!
可以這樣理解沒有錯!
這系列文章主要是以軟體的角度,透過JTAG來操作底層的硬體,
沒有涉入IC設計的範圍!
謝謝您的回覆,這樣就解答我的疑惑了。
這系列文章真的寫得很不錯,很適合剛接觸這個領域的人去了解RISC-V的debugging!